const path = require('path')
const md5 = require('md5')
const fs = require('fs')
const inquirer = require("inquirer")

const logs = require('./log')
const { get, set, merge, escapeRegExp, getDate, hasFile, isFile, isDirectory, mkDirsSync, getFileData, writeFileData } = require('./common')
const LT = require('./letter')

let fsWaitTime = 0
let isNotWatchChange = false

function getConfigLocal () {
    const os = require('os')
    let sysType = os.type()
    const developmentDir = {
        'mp-weixin': '',
        'mp-alipay': ''
    }

    if (sysType === 'Windows_NT') {
        developmentDir['mp-weixin'] = 'C:/Program Files (x86)/Tencent/微信web开发者工具/微信开发者工具.exe'
        developmentDir['mp-alipay'] = 'C:/Program Files (x86)/alipay/小程序开发者工具/小程序开发者工具.exe'
    } else {
        developmentDir['mp-weixin'] = '/Applications/wechatwebdevtools.app/Contents/MacOS'
        developmentDir['mp-alipay'] = '/Applications/小程序开发者工具.app/Contents/MacOS/小程序开发者工具'
    }

    const defLocalContext = `module.exports = {
  'mp-weixin': {
    ideDir: '${developmentDir['mp-weixin']}'
  },
  'mp-alipay': {
    ideDir: '${developmentDir['mp-alipay']}'
  }
}
`
    return defLocalContext
}

async function getConfigFileInfo (config) {
    const info = { name: '', path: '', content: '' }
    const configFileData = await getFileData(config.configFile)
    if (configFileData) {
        configFileData.replace(/import.+(?:\'|")/gi, function (a) {
            const path = (a.split(/from\s+/)[1] || '').replace(/(\'|")/g, '')
            info.path = path
            info.name = path.split('/').pop()
            info.content = configFileData
        })
    }
    return info
}

function getPath (path, data) {
    if (typeof path === 'function') {
        return path(data)
    } else if (Array.isArray(path)) {
        let p = ''
        let res = data
        path.forEach((key, currentIndex) => {
            if (res) {
                if (typeof key === 'function') {
                    if (Array.isArray(res)) {
                        res = res.find((val, index) => {
                            const r = key(val, index)
                            if (r === true) {
                                p = p + (p ? `[${index}]` : index)
                                return true
                            }
                        })
                    } else {
                        for (let k in res) {
                            const r = key(res[k], k)
                            if (r === true) {
                                p = p + (p ? `.` : '') + k
                                res = res[k]
                                break
                            }
                        }
                    }
                } else {
                    res = get(res, key)
                    p = p + (p && key.charAt(0) !== '[' ? '.' : '') + key
                    if (!res && currentIndex === path.length - 1) {
                        res = {}
                    }
                }
            }
        })
        if (!res) {
            p = ''
        }
        return p
    } else if (typeof path === 'string') {
        return path
    } else {
        return ''
    }
}

function getOrReg (regStr) {
    regStr = regStr.split('/')
    if (regStr.length) {
        let r = ''
        regStr.forEach(v => {
            if (v) {
                v = escapeRegExp(v)
                r = r + (r ? `|${v}` : v)
            }
        })
        if (r && regStr.length > 1) {
            r = `(${r})`
        }
        return r
    } else {
        return escapeRegExp(regStr[0])
    }
}

function createReg (regStr) {
    let reg
    if (regStr) {
        reg = ''
        regStr = regStr.trim().split(/\s+/)
        regStr.forEach(v => {
            if (v) {
                reg += `(?=.*${getOrReg(v)})`
            }
        })
        reg = new RegExp(reg, 'i')
    } else {
        reg = /[^\s\S]/
    }
    return reg
}

function selectConfig (configId, mapData) {
    return new Promise((resolve) => {
        if (configId) {
            const choices = []
            const reg = createReg(configId)
            const isPinYin = /^[a-zA-Z\s\/]+$/.test(configId)
            for (let k in mapData) {
                let v = mapData[k]
                if (isPinYin) {
                    v = LT(v, v.length, true)
                }
                if (reg.test(k + ' ' + v)) {
                    choices.push({
                        name: k + ' ' + mapData[k],
                        value: mapData[k]
                    })
                }
            }
            if (choices.length) {
                choices.push({ name: '退出，不选择', value: 'exit' })
                inquirer
                  .prompt({
                      type: 'list',
                      name: 'action',
                      message: `未找到【${configId}】对应配置文件，已模糊匹配到以下配置，请选择`,
                      choices: choices
                  })
                  .then(answer => {
                      const action = answer.action
                      if (action === 'exit') {
                          resolve(undefined)
                      } else {
                          resolve(action)
                      }
                  })
            } else {
                resolve(undefined)
            }
        } else {
            resolve(undefined)
        }
    })
}

module.exports = {
    async ready (cb) {
        const isLocalFile = hasFile('./uniapp.config.local.js')
        if (!isLocalFile) {
            logs.log(`未检测到 uniapp.config.local.js 文件，将自动创建`)
            const res = await writeFileData('./uniapp.config.local.js', getConfigLocal())
            if (res === true) {
                logs.success(`已自动创建 uniapp.config.local.js 文件，您可能需要设置正确的配置，请检查 niapp.config.local.js 文件`)
            } else {
                logs.error('自动创建失败')
                return
            }
        }

        let uniappConfig = await getFileData('./uniapp.config.js')
        if (uniappConfig && /\/uniapp\.config\.local(\.js)?('|")/.test(uniappConfig)) {
            cb()
            return
        } else if (uniappConfig) {
            uniappConfig = `// 本地IDE配置，主要为了解决多人协作时提交Git冲突，所以设置一个本地配置，Git忽略即可\r\nconst LocalConfig = require('./uniapp.config.local.js')\r\n\r\n` + uniappConfig
            logs.log(`uniapp.config.js 缺少对 uniapp.config.local.js 的引用，将自动添加代码`)
            const res = await writeFileData('./uniapp.config.js', uniappConfig)
            if (res === true) {
                logs.success(`已自动为 uniapp.config.js 添加代码，如果是第一次使用或从未修改过uniapp.config.local.js代码，可能需要设置正确的配置，请检查 niapp.config.local.js 文件`)
            } else {
                logs.error('自动添加失败')
                return
            }
        } else {
            logs.error('缺少uniapp.config.js文件')
            return
        }

        logs.warning('请重新运行程序')
    },
    async setManifest (config, configData, configFileName, cwd = global.PRESET_PATH) {
        function setFile (configId, { setWhite, configFile, watchFile, getConfigFile, setConfigFile, custom, before, backUp = false } = {}) {
            return new Promise(async (resolve) => {
                const exec = async ({ setWhite, configFile, watchFile, getConfigFile, setConfigFile, custom, before, backUp = false } = {}) => {
                    let setFile = (typeof setConfigFile === 'function' ? setConfigFile(configData, watchFile, configFile) : setConfigFile) || configFile
                    let getFile = (typeof getConfigFile === 'function' ? getConfigFile(configData, watchFile, configFile) : getConfigFile) || configFile
                    let res = false
                    let isChange = false
                    let oldData = ''
                    let commentsIndex = 0
                    let newData = ''
                    let topStr = ''
                    let sourceData
                    const commentsList = {}
                    const name = setFile

                    if (getFile === false) {
                        logs.warning(`${name} 已被外部接管 ...`)
                        return
                    }

                    if (custom) {
                        if (!hasFile(setFile)) {
                            await writeFileData(setFile, '')
                        }
                    }

                    oldData = await getFileData(setFile)

                    sourceData = getFile === setFile ? oldData : await getFileData(getFile)

                    logs.wait(`${name} init ...`)

                    if (before) {
                        logs.line()
                        logs.warning(`前置处理已被接管`)
                        sourceData = before(sourceData, configData)
                        logs.success(`前置处理结束`)
                        logs.line()
                        isChange = true
                    }

                    if (!custom) {
                        if (oldData === false) {
                            return false
                        }
                        newData = sourceData.replace(/([^{]*)([\s\S]+)/g, function (_, top, json) {
                            topStr = top
                            return json
                        })

                        newData = newData.replace(/\/\/[\s]*保护区[\s\S]+?\/\/[\s]*保护区/g, function ( data) {
                            const k = '_zs_' + (commentsIndex++)
                            commentsList[k] = data
                            return '"' + k + '": "' + k + '",'
                        })

                        newData = newData.replace(/("[^"]+?"[\s]*\:[\s]*[^[{,:]+?)(\/\/|\/\*)/gi, function (_, item, suffix) {
                            return item + ', __placeholder__: "",' + suffix
                        })

                        newData = newData.replace(/(\/\*[\s\S]*?\*\/)|(\/\/[\s]+.*)/gi, function (item) {
                            const k = '_zs_' + (commentsIndex++)
                            commentsList[k] = item
                            return '"' + k + '": "' + k + '",'
                        })

                        try {
                            newData = eval(`(${newData})`)
                        } catch (e) {
                            logs.error('解析出错：' + e.message)
                            return false
                        }
                        for (let k in setWhite) {
                            let val = setWhite[k]
                            let setKey
                            let getKey
                            let defaultValue

                            if (typeof val === 'object' && ((val.setPath && val.getPath) || val.custom)) {
                                if (val.custom) {
                                    val.custom(newData, configData)
                                } else {
                                    setKey = getPath(val.setPath, newData)
                                    getKey = getPath(val.getPath, configData)
                                    defaultValue = val.default
                                }
                            } else {
                                setKey = k
                                getKey = val
                                if (typeof getKey === 'object') {
                                    defaultValue = getKey.default
                                    getKey = getKey.path
                                }
                            }
                            if (setKey) {
                                if (defaultValue === undefined) {
                                    defaultValue = ''
                                }
                                set(newData, setKey, get(configData, getKey, defaultValue))
                                isChange = true
                            }
                        }

                        config[getFile] = newData

                        newData = JSON.stringify(newData, null, 2)
                        newData = newData.replace(/"(_zs_[\d]+)[^,\r\n]+,?/gi, function (zs, key) {
                            return commentsList[key] || ''
                        })
                        newData = newData.replace(/,[\s]*"__placeholder__[^,]+?,/gi, '')
                    } else {
                        newData = custom({ oldData, currentData: sourceData, config: configData, setFile, getFile, configFile, watchFile })
                        isChange = true
                    }

                    if (oldData === topStr + newData) {
                        logs.success(`检测到 ${name} 配置相同（${configId || ''} ${configFileName}），跳过更新`)
                        return true
                    }

                    if (isChange) {
                        logs.log(`正在配置 ${name}`)
                        if (backUp) {
                            const copyFile = setFile + '.copy'
                            const isCopyHas = hasFile(copyFile)
                            if (!isCopyHas) {
                                logs.log(`未发现 ${name} 备份文件，正在备份...`)
                                const cpRes = await writeFileData(copyFile, '// 备份于本地时间 ' + getDate() + '\n// 仅会备份一次，如需重新备份，请删除此文件后重新运行项目即可重新备份\n\n' + oldData)
                                const m = `${name} 配置文件备份` + (cpRes ? '成功' : '失败')
                                if (cpRes) {
                                    logs.success(m, copyFile)
                                } else {
                                    logs.error(m, copyFile)
                                }
                            }
                        }
                        res = await writeFileData(setFile, topStr + newData)
                        const m = `${name} 配置` + (res ? '成功' : '失败')
                        if (res) {
                            logs.success(m, setFile)
                        } else {
                            logs.error(m, setFile)
                        }
                    } else {
                        logs.error(`未检测到可以配置的 ${name} 信息`)
                    }
                    logs.line()
                    return res
                }

                if (configFile && watchFile && isDirectory(watchFile) && !/[^.]+\.[^.]+($|\/)/.test(configFile)) {
                    let r = true
                    const deepSet = async (sourcePath, targetPath) => {
                        const sourceFiles = fs.readdirSync(path.resolve(global.PRESET_PATH, sourcePath), { withFileTypes: true })
                        mkDirsSync(targetPath)
                        for (let i = 0; i < sourceFiles.length; i++) {
                            const file = sourceFiles[i]
                            const newSourcePath = path.join(sourcePath, file.name)
                            const newTargetPath = path.join(targetPath, file.name)

                            if (file.isDirectory()) {
                                mkDirsSync(newTargetPath)
                                await deepSet(newSourcePath, newTargetPath)
                            } else {
                                try {
                                    const rr = await exec({
                                        setWhite: undefined,
                                        configFile: newTargetPath,
                                        watchFile: newSourcePath,
                                        getConfigFile: getConfigFile || newSourcePath,
                                        setConfigFile: setConfigFile || newTargetPath,
                                        custom: custom,
                                        before: undefined,
                                        backUp: false
                                    })
                                    if (r === true) {
                                        r = rr
                                    }
                                } catch (e) {
                                    r = false
                                }
                            }
                        }
                    }
                    await deepSet(watchFile, configFile)
                    resolve(r)
                } else {
                    resolve(await exec({
                        setWhite,
                        configFile,
                        watchFile,
                        getConfigFile,
                        setConfigFile,
                        custom,
                        before,
                        backUp
                    }))
                }
            })
        }
        let allRes = true
        async function loop (configId, list, resolve) {
            const cfg = list.shift()
            const res = await setFile(configId, cfg)
            if (!res) {
                allRes = res
            }
            if (list && list.length) {
                loop(configId, list, resolve)
            } else {
                if (allRes) {
                    logs.log('当前使用配置', (configId || '').red, configFileName.cyan)
                }
                resolve(allRes)
            }
        }
        return new Promise(async (resolve) => {
            loop(config.configId, [...config.extend], resolve)
        })
    },
    async setConfig (config, configData, configFileName, cwd = global.PRESET_PATH) {
        return new Promise(async (resolve) => {
            let res = true
            const configFileInfo = await getConfigFileInfo(config)
            const fileContent = `import configs from './${configFileName}'\nexport default configs\n`
            logs.wait('config init ...')

            if (fileContent === configFileInfo.content) {
                logs.success(`检测到 ${config.configFile} 配置相同，跳过更新`)
            } else {
                logs.log('正在配置 config')
                res = await writeFileData(config.configFile, fileContent)
                if (res) {
                    logs.success('config 配置成功', config.configFile)
                    isNotWatchChange = true
                } else {
                    logs.error('config 配置失败')
                }
            }

            resolve(res)
        })
    },
    async updateManifest () {
        const config = await this.getCompanyConfig()
        const configFileInfo = await getConfigFileInfo(config)

        if (configFileInfo.path && !/\.js/.test(configFileInfo.path)) {
            configFileInfo.path += '.js'
        }

        const configData = await this.getConfigData(path.join(config.configDir, configFileInfo.path))
        if (configData) {
            await this.setManifest(config, configData, configFileInfo.path)
        }
    },
    async getConfigData (file, config = undefined) {
        return new Promise(async (resolve) => {
            let defaultConfig
            if (!config) {
                config = await this.getCompanyConfig()
            }
            if (config && config.defaultConfigFile) {
                defaultConfig = await getFileData(config.defaultConfigFile)
                if (defaultConfig !== false) {
                    defaultConfig = defaultConfig.replace(/[^{]+([\S\s]+})[\s\S]*/gi, '$1')
                    defaultConfig = eval('(' + defaultConfig + ')')
                } else {
                    defaultConfig = undefined
                }
            }
            let configData = await getFileData(file)
            if (configData !== false) {
                configData = configData.replace(/[^{]+([\S\s]+})[\s\S]*/gi, '$1')
                configData = eval('(' + configData + ')')

                if (defaultConfig) {
                    configData = merge(defaultConfig, configData)
                }
            }
            resolve(configData)
        })
    },
    async getCompanyConfig (configId) {
        return new Promise(async (resolve) => {
            const file = './uniapp.config.js'
            let config = {}
            const isUniConfigHas = hasFile(file)
            if (isUniConfigHas) {
                config = await getFileData(file)
                config = require(path.join(global.PRESET_PATH, file))
                config.configId = configId
                config.configFile = config.configDir + '/index.js'
            }
            resolve(config)
        })
    },
    async setCompanyConfig (config) {
        return new Promise(async (resolve) => {
            let verifyRes = true
            let configData
            const props = ['configDir', 'mapFile', 'extend']
            props.forEach(key => {
                if (!config[key]) {
                    verifyRes = false
                }
            })
            if (verifyRes) {
                const mapData = require(path.join(global.PRESET_PATH, config.mapFile)) || {}
                let configFileName = mapData[config.configId]
                if (!configFileName) {
                    configFileName = await selectConfig(config.configId, mapData)
                }

                let configFile = configFileName
                if (configFile && !/\.js/.test(configFile)) {
                    configFile += '.js'
                }

                if (configFile) {
                    let doneMsg = 'warning 自动化配置任务未全部成功执行，请手动检查配置是否正确'.yellow
                    let endFlag = '×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××'.yellow
                    configData = await this.getConfigData(config.configDir + '/' + configFile, config)
                    if (configData) {
                        logs.log('@description：uni-app自动化配置方案')
                        logs.log('@author：梭哈')
                        logs.line()
                        logs.log('配置', (config.configId + '').red, configFileName.cyan)
                        const res1 = await this.setConfig(config, configData, configFileName)
                        const res2 = await this.setManifest(config, configData, configFileName)
                        if (res1 && res2) {
                            doneMsg = 'success 自动化配置任务已全部完成'.green
                            endFlag = '√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√'.green
                        }
                    }
                    logs.success(doneMsg)
                    logs.success(endFlag)
                } else {
                    logs.error('未找到【' + config.configId + '】对应配置文件，请使用 ' + logs.helpTip )
                }
                logs.line()
            }
            if (configData === false) {
                config = false
            }
            config.configData = configData
            resolve(config)
        })
    },
    watch (file, options = {}) {
        let preveMd5 = null
        const filePath = file
        const isWatchFile = /(\\|\/)[^\\/]+\..+$/gi.test(file)
        logs.log('Watching'.yellow, file.cyan)

        fs.watch(filePath, options, (event, filename) => {
            if (filename && event === 'change') {
                const file = isWatchFile ? filePath : path.join(filePath, filename)
                try {
                    if (fs.statSync(file).isFile()) {
                        if (!isNotWatchChange) {
                            clearTimeout(fsWaitTime)
                            fsWaitTime = setTimeout(() => {
                                let currentMd5
                                try {
                                    currentMd5 = md5(fs.readFileSync(file))
                                } catch (e) {
                                }
                                if (currentMd5 == preveMd5) {
                                    return
                                }
                                preveMd5 = currentMd5
                                this.updateManifest()
                            }, 3)
                        } else {
                            isNotWatchChange = false
                        }
                    }
                } catch (e) {
                }
            }
        })
    },
    async viewMap () {
        const config = await this.getCompanyConfig()
        let mapData = {}
        if (config.mapFile) {
            mapData = require(path.join(global.PRESET_PATH, config.mapFile)) || {}
        }
        logs.log('配置映射', mapData)
        logs.log('使用：', 'yarn <scripts> <key> 请将key放在命令最后 如：yarn wx test'.yellow)
    },
    async viewCurrent () {
        let configData
        const config = await this.getCompanyConfig()
        const configFileInfo = await getConfigFileInfo(config)

        let oldData = await getFileData('./src/manifest.json')
        const manifest = oldData ? oldData.match(/"description": "(.+?)"/) : ''

        logs.log('当前使用配置')
        logs.log({
            config: configFileInfo.name,
            manifest: manifest ? manifest[1] : ''
        })

        if (configFileInfo.path) {
            if (!/\.js/.test(configFileInfo.path)) {
                configFileInfo.path += '.js'
            }
            configData = await this.getConfigData(path.join(config.configDir, configFileInfo.path))
        }

        logs.log('configData =>', configData)
    },
    async viewItem (configId) {
        const config = await this.getCompanyConfig()
        const mapData = require(path.join(global.PRESET_PATH, config.mapFile)) || {}
        let configFile = mapData[configId]

        if (!configFile) {
            configFile = await selectConfig(configId, mapData)
        }

        if (!/\.js/.test(configFile)) {
            configFile += '.js'
        }
        const configData = await this.getConfigData(config.configDir + '/' + configFile)
        logs.log(configId + ' =>', configData)
    }
}
